package com.ikingtech.framework.sdk.log.embedded.appender.consumer;

import ch.qos.logback.classic.pattern.ExtendedThrowableProxyConverter;
import ch.qos.logback.classic.spi.ILoggingEvent;
import com.ikingtech.framework.sdk.enums.common.FrameworkAgentTypeEnum;
import com.ikingtech.framework.sdk.log.model.rpc.SystemLogBatchReportParam;
import com.ikingtech.framework.sdk.log.model.rpc.SystemLogReportParam;
import com.ikingtech.framework.sdk.utils.Tools;
import com.ikingtech.framework.sdk.web.support.agent.FrameworkAgentProxy;
import lombok.extern.slf4j.Slf4j;

import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;

/**
 * @author tie yan
 */
@Slf4j
public class LogConsumer extends Thread {

    protected static final BlockingQueue<ILoggingEvent> LOG_CACHE_L1 = new LinkedBlockingQueue<>(100000);

    protected static final ArrayBlockingQueue<SystemLogReportParam> LOG_CACHE_L2 = new ArrayBlockingQueue<>(100000);

    private boolean stop = false;

    public LogConsumer() {
        super.setName("log-consumer");
    }

    public static void cacheLog(ILoggingEvent iLoggingEvent) {
        LOG_CACHE_L1.add(iLoggingEvent);
    }

    @Override
    public void run() {
        ILoggingEvent loggingEvent;
        new LogFlusher().start();
        while (!stop) {
            try {
                loggingEvent = LOG_CACHE_L1.take();
                if (!loggingEvent.getFormattedMessage().regionMatches(0, "[N/A]", 0, 5)) {
                    LOG_CACHE_L2.add(this.convert(loggingEvent));
                }
            } catch (InterruptedException e) {
                this.down();
                Thread.currentThread().interrupt();
            }
        }
    }

    public void down() {
        this.stop = true;
    }

    private static class LogFlusher extends Thread {

        public LogFlusher() {
            super.setName("sys-log-flush");
        }

        private boolean stop = false;

        @Override
        public void run() {
            while (!stop) {
                if (!LOG_CACHE_L2.isEmpty()) {
                    List<SystemLogReportParam> logReportParams = new ArrayList<>();
                    LOG_CACHE_L2.drainTo(logReportParams);
                    SystemLogBatchReportParam reportParam = new SystemLogBatchReportParam();
                    reportParam.setLogReportParams(logReportParams);
                    FrameworkAgentProxy.agent().execute(FrameworkAgentTypeEnum.SYSTEM_LOG, reportParam);
                }
                try {
                    TimeUnit.SECONDS.sleep(5);
                } catch (InterruptedException e) {
                    this.down();
                    Thread.currentThread().interrupt();
                }
            }
        }

        public void down() {
            this.stop = true;
        }
    }

    private SystemLogReportParam convert(ILoggingEvent loggingEvent) {
        SystemLogReportParam reportParam = new SystemLogReportParam();

        reportParam.setPackageName(loggingEvent.getLoggerName());

        StackTraceElement[] callerData = loggingEvent.getCallerData();
        if (callerData != null && callerData.length > 0) {
            reportParam.setMethod(callerData[0].getMethodName());
        }
        reportParam.setTraceId(loggingEvent.getMDCPropertyMap().get("traceId"));
        reportParam.setMessage(loggingEvent.getFormattedMessage());
        if (Tools.Str.isNotBlank(reportParam.getMessage()) &&
                reportParam.getMessage().startsWith("[") &&
                reportParam.getMessage().indexOf("]") > 0) {
            List<String> tags = Tools.Str.split(reportParam.getMessage().substring(reportParam.getMessage().indexOf("[") + 1, reportParam.getMessage().indexOf("]")), " | ");
            reportParam.setDomainCode(!tags.isEmpty() ? tags.get(0) : Tools.Str.EMPTY);
            reportParam.setTenantCode(tags.size() > 1 ? tags.get(1) : Tools.Str.EMPTY);
            reportParam.setAppCode(tags.size() > 2 ? tags.get(2) : Tools.Str.EMPTY);
        }
        if (loggingEvent.getThrowableProxy() != null) {
            ExtendedThrowableProxyConverter converter = new ExtendedThrowableProxyConverter();
            converter.start();
            String stackInfo = converter.convert(loggingEvent);
            reportParam.setExceptionStack(loggingEvent.getThrowableProxy().getMessage() + System.lineSeparator() + stackInfo);
            converter.stop();
        }
        reportParam.setExecuteTime(LocalDateTime.ofInstant(Instant.ofEpochMilli(loggingEvent.getTimeStamp()), ZoneId.systemDefault()));
        return reportParam;
    }
}
